package org.catspaw.cherubim.persistence.dao.spring.mybatis;

import static org.catspaw.cherubim.persistence.mybatis.Constants.BASE_NAMESPACE_PREFIX;
import static org.catspaw.cherubim.persistence.mybatis.Constants.COLUMN_PARAM;
import static org.catspaw.cherubim.persistence.mybatis.Constants.CONDITIONS_PARAM;
import static org.catspaw.cherubim.persistence.mybatis.Constants.TABLE_PARAM;
import static org.catspaw.cherubim.persistence.mybatis.Constants.VALUES_PARAM;
import static org.catspaw.cherubim.persistence.mybatis.Constants.VALUE_PARAM;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.mybatis.spring.support.SqlSessionDaoSupport;

import org.catspaw.cherubim.persistence.dao.ExtendedDao;
import org.catspaw.cherubim.persistence.mapping.EntityMappingResolver;
import org.catspaw.cherubim.persistence.mybatis.Constants;
import org.catspaw.cherubim.util.GenericTypeUtils;
import org.catspaw.cherubim.util.KeyValuePair;
import org.catspaw.cherubim.util.QuietPropertyUtils;

public class BaseSpringMybatisDao<E, P> extends SqlSessionDaoSupport implements
		ExtendedDao<E, P> {

	private EntityMappingResolver	resolver;
	private Class<E>				entityClass;	//model的class，即参数化类型<E>
	private Class<P>				idClass;

	protected Class<E> getEntityClass() {
		if (entityClass == null) {
			entityClass = (Class<E>) GenericTypeUtils
					.getSuperClassGenericType(this.getClass());
			logger.info("entity class not initialized, use generic class: "
					+ getEntityClass().getName());
		}
		return entityClass;
	}

	protected Class<P> getIdClass() {
		if (idClass == null) {
			idClass = (Class<P>) GenericTypeUtils.getSuperClassGenericType(this
					.getClass(), 1);
			logger.info("id class not initialized, use generic class: "
					+ getIdClass().getName());
		}
		return idClass;
	}

	protected void setEntityClass(Class<E> entityClass) {
		this.entityClass = entityClass;
	}

	protected void setIdClass(Class<P> idClass) {
		this.idClass = idClass;
	}

	public E create() {
		try {
			return getEntityClass().newInstance();
		} catch (InstantiationException e) {
			throw new IllegalStateException(e);
		} catch (IllegalAccessException e) {
			throw new IllegalStateException(e);
		}
	}

	public Number total() {
		return (Number) getSqlSession()
				.selectOne(Constants.BASE_NAMESPACE_PREFIX + "total",
							resolver.getTable());
	}

	public E get(P id) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put(Constants.TABLE_PARAM, resolver.getTable());
		params.put(Constants.ID_COLUMN_PARAM, resolver.getIdColumn());
		params.put("id", id);
		return (E) getSqlSession().selectOne(
												Constants.BASE_NAMESPACE_PREFIX
														+ "get", params);
	}

	public void save(E entity) {
		saveAndObtainId(entity);
	}

	public P saveAndObtainId(E entity) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put(Constants.TABLE_PARAM, resolver.getTable());
		String[] columns = resolver.getColumns();
		params.put(Constants.COLUMNS_PARAM, columns);
		String[] properties = resolver.getProperties();
		Object[] values = new Object[properties.length];
		for (int i = 0; i < properties.length; i++) {
			values[i] = QuietPropertyUtils.getPropertyQuietly(entity,
																properties[i]);
		}
		params.put(Constants.VALUES_PARAM, values);
		getSqlSession()
				.insert(Constants.BASE_NAMESPACE_PREFIX + "save", params);
		return null;
	}

	public void update(E entity) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put(Constants.TABLE_PARAM, resolver.getTable());
		String[] columns = resolver.getColumns();
		String[] properties = resolver.getProperties();
		KeyValuePair[] kvs = new KeyValuePair[columns.length];
		for (int i = 0; i < properties.length; i++) {
			Object value = QuietPropertyUtils
					.getPropertyQuietly(entity, properties[i]);
			kvs[i] = new KeyValuePair(columns[i], value);
		}
		params.put(Constants.SETS_PARAM, kvs);
		String idProperty = resolver.getIdProperty();
		Object id = QuietPropertyUtils.getPropertyQuietly(entity, idProperty);
		params.put(Constants.ID_COLUMN_PARAM, id);
		getSqlSession().update(Constants.BASE_NAMESPACE_PREFIX + "update",
								params);
	}

	public void delete(E entity) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put(Constants.TABLE_PARAM, resolver.getTable());
		String idProperty = resolver.getIdProperty();
		Object id = QuietPropertyUtils.getPropertyQuietly(entity, idProperty);
		params.put(Constants.ID_COLUMN_PARAM, id);
		getSqlSession().delete(Constants.BASE_NAMESPACE_PREFIX + "delete",
								params);
	}

	public List<E> findAll() {
		List<Map<String, Object>> mapList = getSqlSession()
				.selectList(Constants.BASE_NAMESPACE_PREFIX + "findAll",
							resolver.getTable());
		List<E> entities = new ArrayList<E>(mapList.size());
		for (Map<String, Object> map : mapList) {
			E entity = create();
			for (Map.Entry<String, Object> entry : map.entrySet()) {
				String property = resolver.getProperty(entry.getKey());
				QuietPropertyUtils.setPropertyQuietly(entity, property, entry
						.getValue());
			}
			entities.add(entity);
		}
		return entities;
	}

	public List<E> findByExample(E entity) {
		throw new UnsupportedOperationException();
	}

	public List<E> findByProperties(String[] names, Object[] values) {
		KeyValuePair[] pairs = new KeyValuePair[names.length];
		for (int i = 0; i < names.length; i++) {
			String column = getEntityMappingResolver().getColumn(names[i]);
			pairs[i] = new KeyValuePair(column, values[i]);
		}
		Map<String, Object> params = new HashMap<String, Object>();
		params.put(TABLE_PARAM, getEntityMappingResolver().getTable());
		params.put(CONDITIONS_PARAM, pairs);
		return findList(BASE_NAMESPACE_PREFIX + "findByProperties", params);
	}

	public List<E> findByProperty(String name, Object value) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put(TABLE_PARAM, getEntityMappingResolver().getTable());
		String column = getEntityMappingResolver().getColumn(name);
		params.put(COLUMN_PARAM, column);
		params.put(VALUE_PARAM, value);
		return findList(BASE_NAMESPACE_PREFIX + "findByProperty", params);
	}

	public List<E> findInProperties(String name, Object[] value) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put(TABLE_PARAM, getEntityMappingResolver().getTable());
		String column = getEntityMappingResolver().getColumn(name);
		params.put(COLUMN_PARAM, column);
		params.put(VALUES_PARAM, value);
		return findList(BASE_NAMESPACE_PREFIX + "findInProperties", params);
	}

	public E findByUnique(String name, Object value) {
		List<E> list = findByProperty(name, value);
		if (list.size() > 0) {
			return list.get(0);
		}
		return null;
	}

	public List<E> findList(String queryId, Map<String, Object> params) {
		List<Map<String, Object>> mapList = getSqlSession().selectList(queryId,
																		params);
		List<E> entities = new ArrayList<E>(mapList.size());
		for (Map<String, Object> map : mapList) {
			E entity = mapToEntity(map);
			entities.add(entity);
		}
		return entities;
	}

	public E findOne(String queryId, Map<String, Object> params) {
		Map<String, Object> map = (Map<String, Object>) getSqlSession()
				.selectOne(queryId, params);
		return mapToEntity(map);
	}

	private E mapToEntity(Map<String, Object> map) {
		E entity = create();
		for (Map.Entry<String, Object> entry : map.entrySet()) {
			String property = getEntityMappingResolver()
					.getProperty(entry.getKey());
			QuietPropertyUtils.setPropertyQuietly(entity, property, entry
					.getValue());
		}
		return entity;
	}

	public Number countByProperties(String[] names, Object[] values) {
		KeyValuePair[] pairs = new KeyValuePair[names.length];
		for (int i = 0; i < names.length; i++) {
			String column = getEntityMappingResolver().getColumn(names[i]);
			pairs[i] = new KeyValuePair(column, values[i]);
		}
		Map<String, Object> params = new HashMap<String, Object>();
		params.put(TABLE_PARAM, getEntityMappingResolver().getTable());
		params.put(CONDITIONS_PARAM, pairs);
		return (Number) getSqlSession()
				.selectOne(BASE_NAMESPACE_PREFIX + "countByProperties", params);
	}

	public Number countByProperty(String name, Object value) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put(TABLE_PARAM, getEntityMappingResolver().getTable());
		String column = getEntityMappingResolver().getColumn(name);
		params.put(COLUMN_PARAM, column);
		params.put(VALUE_PARAM, value);
		return (Number) getSqlSession()
				.selectOne(BASE_NAMESPACE_PREFIX + "countByProperty", params);
	}

	public Number countInProperties(String name, Object[] value) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put(TABLE_PARAM, getEntityMappingResolver().getTable());
		String column = getEntityMappingResolver().getColumn(name);
		params.put(COLUMN_PARAM, column);
		params.put(VALUES_PARAM, value);
		return (Number) getSqlSession()
				.selectOne(BASE_NAMESPACE_PREFIX + "countInProperties", params);
	}

	public void deleteByProperties(String[] names, Object[] values) {
		KeyValuePair[] pairs = new KeyValuePair[names.length];
		for (int i = 0; i < names.length; i++) {
			String column = getEntityMappingResolver().getColumn(names[i]);
			pairs[i] = new KeyValuePair(column, values[i]);
		}
		Map<String, Object> params = new HashMap<String, Object>();
		params.put(TABLE_PARAM, getEntityMappingResolver().getTable());
		params.put(CONDITIONS_PARAM, pairs);
		getSqlSession().delete(BASE_NAMESPACE_PREFIX + "deleteByProperties",
								params);
	}

	public void deleteByProperty(String name, Object value) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put(TABLE_PARAM, getEntityMappingResolver().getTable());
		String column = getEntityMappingResolver().getColumn(name);
		params.put(COLUMN_PARAM, column);
		params.put(VALUE_PARAM, value);
		getSqlSession().delete(BASE_NAMESPACE_PREFIX + "deleteByProperty",
								params);
	}

	public void deleteInProperties(String name, Object[] value) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put(TABLE_PARAM, getEntityMappingResolver().getTable());
		String column = getEntityMappingResolver().getColumn(name);
		params.put(COLUMN_PARAM, column);
		params.put(VALUES_PARAM, value);
		getSqlSession().delete(BASE_NAMESPACE_PREFIX + "deleteInProperties",
								params);
	}

	public EntityMappingResolver getEntityMappingResolver() {
		return resolver;
	}
}
